何為程式碼異味? 程式碼又是如何散發「臭味」?
在「重構:改善既有程式的設計」 這本書有提到,程式碼異味(Code Smell)是在講說,程式碼異味代表著一個跡象:「這裡有個可以用重構來解決的問題」。
而異味本身並不代表「問題」,而是問題的指標。或者你可以這麼想,有問題的不是刺鼻的臭味,而是那堆散發臭味的垃圾。
Bob 大叔在他的部落格提到:「我看到一次超過十行的 Java 程式碼,鼻頭就會開始皺起來」,這就是一個很經典,相當直覺聞到異味的跡象,接下來我們就先來看看,到底我們的程式碼有什麼樣的「異味」。
從 Code Smell 的清單參考幾個來看看 🚭 參考了常見的前 10 名異味清單,如下所示:
以上 10 個是常見的 Code Smell 清單,10 個是有點多,看到有點眼花。其中有幾項蠻接近的,因此我整理了一下,大概可以分成這 3 類異味:
鏡頭轉回棚內,回頭看一下 App 的程式碼,先用肉眼掃描一下,有沒有 Bob 大叔提到的,一看到就會捏起鼻子的 Code 呢?
看了一眼,其實沒有想像中的那麼多 😂
說真的,請 AI 產出程式碼,已經避免掉很多常見的程式碼異味問題,不太會犯初學者常犯的那些錯誤。比方說,不太會有程式碼都寫成一坨的義大利麵程式碼(Spaghetti code)問題發生。AI 生成的結果,倒是結構頗為分明,每個組件打從一出生,就清清楚楚被分配好職責。
不過,雖然肉眼看不出來,那還是問一下 AI 的意見好了。雖然程式碼也是它寫出來的,但 LLM 不會像人類那樣有「宜家效應」,覺得自己寫的 Code 比較好而抗拒調整。而且你可以想像說,每次回答,都是不同的回答者在回答,所以要多問幾次。
此外,不同 LLM 模型之間的回答差異會更大,所以多問幾次,就可以把結果逼近取得最大的交集,以取得最好的結果。
試著請 claude-sonnet-3.5 模型給一些建議:
總觀這個程式碼檔案,你可以看出有哪些 Code Smell? 並提供一些修改建議,謝謝。
AI 瞄了程式碼檔案,瞬間給了以下回覆
1. 組件過大:
`App` 組件太大且複雜,包含了太多的狀態和邏輯。這可能會導致維護困難。
- 建議:將 `App` 組件拆分成更小的、可重用的組件。例如,可以創建一個單獨的 `Calculator` 組件來處理計算邏輯。
2. 重複的樣式代碼:
在多個地方重複使用了相似的樣式類。
- 建議:創建一個共用的樣式文件或使用 CSS-in-JS 解決方案來減少重複。
3. 硬編碼的字符串:
類別名稱和按鈕文本直接寫在代碼中。
- 建議:將這些字符串提取到常量或配置文件中,以便於國際化和維護。
4. 未使用的函數:
`calculateFoodCatogoryCount` 函數被定義但從未使用。
- 建議:如果不需要,刪除這個函數;如果需要,確保在適當的地方使用它。
5. 複雜的條件渲染:
在 `App` 組件中使用了條件渲染來切換 `CategorySelector` 和 `Keypad`。
- 建議:考慮使用狀態機或路由來管理這種複雜的 UI 狀態。
6. 重複的邏輯:
處理編輯和添加新項目的邏輯有些重複。
- 建議:將這些邏輯提取到單獨的函數中,以減少重複並提高可讀性。
7. 缺乏錯誤處理:
沒有看到明顯的錯誤處理邏輯,例如處理無效輸入。
- 建議:添加適當的錯誤處理和用戶反饋機制。
8. 過多的內聯樣式:
有些組件,如 `Keypad`,包含了大量的內聯樣式邏輯。
- 建議:考慮使用 CSS 模塊或 styled-components 來更好地組織樣式。
9. 缺乏類型檢查:
雖然使用了 TypeScript,但有些地方可以改進類型定義,例如 `HistoryItem` 類型。
- 建議:確保所有的類型都被正確定義和使用,以充分利用 TypeScript 的優勢。
10. 狀態管理複雜:
`App` 組件中有多個相互關聯的狀態,這可能導致難以追踪狀態變化。
- 建議:考慮使用 Redux 或 Context API 來集中管理狀態,特別是如果應用程序進一步擴大。
綜觀以上的建議,有些是因為還在開發途中(寫到一半還沒整理好),還在持續調整,撇除這些因素的程式碼,整理出目前程式碼具有「異味」的部分:
從上到下「晃了一下」程式碼,發覺有幾個地方不是很好理解(臭臭!捏起鼻子)。
首先是這一塊,一個深層巢狀結構,光看就有點反胃(嘔)。雖然很清楚這一塊在做什麼,但是哪天要修改其中一個按鈕的樣式,我會被這塊邏輯給嚇到,愣在那邊不知該如何動刀 🥸。
<button
key={index}
className={`p-4 text-xl font-bold rounded ${
btn === "AC"
? "bg-orange-500 text-white"
: btn === "⌫"
? "bg-yellow-500 text-white"
: btn === "+"
? "bg-gray-600 text-white"
: "bg-gray-700 text-white"
}`}
//...
//...
onClick={() => {
if (btn === "AC") onClear();
else if (btn === "⌫") onBackspace();
else onInput(btn);
}}
在往下看一點,上面這一塊負責觸發行為的程式碼,也是有類似的問題。剛好蠻像上面提到「物件導向」那一種異味的「善用多態」,但先留著,晚點再來動手調整 👍
最後,還有一個地方如 AI 回覆的那樣,不過我關注的點不是寫了比較多的 if / else ,而是 if 的嵌套邏輯有點難懂,兩層的 if 在其中,若無法避免這樣寫 if / else,是不是可以讓程式邏輯更好懂一點?
所以我覺得這邊也有點臭臭的,但還不急著動手,但可以等晚點將邏輯(職責)拆分出去後,說不定 if / else 就消失了
const handleSelectCategory = (category: string) => {
if (pendingAmount !== null) {
if (editingItem) {
// Edit existing item
setHistory(history.map(item =>
item.id === editingItem.id ? { ...item, amount: pendingAmount, category } : item
));
setEditingItem(null);
} else {
// Add new item
const newItem = { amount: pendingAmount, category, id: Date.now() };
setHistory([...history, newItem]);
}
setPendingAmount(null);
}
setShowCategorySelector(false);
};
那你會想說,程式碼異味有沒有一個簡單判斷的通則?算是有吧,想分享一個蠻簡單的方法,來判斷程式碼是不是有異味:
如果回頭看了三番兩次,還不是很理解,需要請 AI 解釋的話(或是請別人幫忙解說),代表寫得不夠清楚,此段程式八成就是散發著異味了。
重構:改善既有程式的設計一書中,最後的附錄表列了「不只 10 種」的異味清單,且每種 Code Smell 都有對應的重構方法,蠻推薦大家去讀的喔,相當經典的重構好書。
接下來,從這些異味點(Code Smell Spots)出發,著手進行重構囉!